/**
 * 
 */
package truerefactor.refactor;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import truerefactor.graph.Accessibility;
import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;
import truerefactor.graph.TypePair;
import truerefactor.graph.SetOperations;

/**
 * ExtractInterface - An implementation of the Extract Interface Refactoring
 * technique
 * 
 * @author Isaac Griffith
 */
public class ExtractInterface extends Refactoring {

    /**
     * Creates a new ExtractInterface instance
     */
    public ExtractInterface()
    {
        this.shortName = "EXIN";
        this.name = "Extract Interface";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeNode [])
     */
    @Override
    public void execute(CodeGraph graph)
    {
        // 1. Create an empty interface
        ClassNode intrface = new ClassNode("I" + operands.get(0).getIdentifier(), "");
        intrface.setInterface(true);
        intrface.setAccessibility(Accessibility.Public);

        // 2. Declare the common operations in the interface
        Set<CodeNode> methodSets[] = new HashSet[operands.size()];
        for (int i = 0; i < operands.size(); i++)
        {
            methodSets[i] = new HashSet<CodeNode>();
            methodSets[i].addAll(((ClassNode) operands.get(i)).getMethods());
        }

        Set<CodeNode> commonOps = new HashSet<CodeNode>();
        if (methodSets.length >= 2)
        {
            commonOps = SetOperations.intersection(methodSets[0], methodSets[1]);
        }

        for (int i = 3; i < methodSets.length - 1; i++)
        {
            Set<CodeNode> temp = SetOperations.union(commonOps, methodSets[i]);
            commonOps = temp;
        }

        List<CodeNode> interfaceOps = new ArrayList<CodeNode>();
        for (CodeNode node : commonOps)
        {
            if (node instanceof MethodNode)
            {
                MethodNode mNode = new MethodNode(node.getIdentifier(), "");

                List<String> params = ((MethodNode) node).getParameters();
                List<TypePair> paramInfo = ((MethodNode) node).getAllParamInfo();
                for (int i = 0; i < params.size(); i++)
                {
                    mNode.addParameter(params.get(i), paramInfo.get(i).getType(), paramInfo.get(i).isPrimitive());
                }

                mNode.setParentClass(intrface);

                interfaceOps.add(mNode);
                graph.insert(mNode);
                graph.insert(new Edge(intrface, mNode, EdgeType.Method));
                intrface.add(mNode);
            }
        }

        // 3. Declare the relevant class(es) as implementing the interfaces
        for (CodeNode node : operands)
        {
            graph.insert(new Edge(intrface, node, EdgeType.InterfaceRealization));
            ClassNode cNode = (ClassNode) node;
            cNode.addParentClass(intrface);
        }

        // 4. Adjust the client type declarations to use the interface.
        List<ClassNode> clients = new ArrayList<ClassNode>();
        List<ClassNode> classes = graph.getClasses();
        for (ClassNode cNode : classes)
        {
            for (CodeNode node : operands)
            {
                if (node instanceof ClassNode)
                {
                    if (graph.isEdge(cNode, (ClassNode) node, EdgeType.Association))
                    {
                        clients.add(cNode);

                    }
                }
            }
        }

        for (ClassNode client : clients)
        {
            List<CodeNode> fields = client.getAttributes();

            for (CodeNode field : fields)
            {
                if (field instanceof AttributeNode)
                {
                    AttributeNode attr = (AttributeNode) field;

                    ClassNode type = attr.getAssociatedClass();
                    if (type != null)
                    {
                        if (operands.contains(type))
                        {
                            attr.setType(intrface.getIdentifier());
                            attr.setAssociatedClass(intrface);
                        }
                    }
                }
            }
        }

    }
}
